/*
 * ======== testudp.c ========
 * Test the crude echo by sending to port 7 and waiting
 * for replies.
 */

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include "sockets.h"

#define UDP_LOW   1

/*
 * Usage: testUdp [ip] [port] [len] [loops] [round] [mode]
 *
 *
 * Maximum payload sizes over UDP
 *
 * A payload greater than 1472 bytes (IPv4) or 1452 (IPv6) requires a packet
 * size greater than 1514 bytes, which is the maximum over Ethernet.
 *
 * Ethernet Frame for IPv4:
 *
 *     [EthHdr (14)] [UdpHdr (8)] [Ipv4Hdr (20)] [payload (1472)]
 *
 *     (1514 = 14 + 8 + 20 + 1472)
 *
 * Ethernet Frame for IPv6:
 *
 *     [EthHdr (14)] [UdpHdr (8)] [Ipv6Hdr (40)] [payload(1452)]
 *
 *     (1514 = 14 + 8 + 40 + 1452)
 *
 */
#define UDP_IPv4_MAXPAYLOAD  1472
#define UDP_IPv6_MAXPAYLOAD  1452

#define BUFSIZE     (1024+512)
#define PORT        7
#define ROUND       10
#define LOOPS       100000
#define ECMAX       LOOPS//TODO FIXME

#define TIMEOUT     1//1s time out

char txBuff[2][BUFSIZE];
char rxBuff[BUFSIZE];

const char* defIp = "10.0.0.172";

//
//static void clearRecv(SOCKET s) {
//    short l;
//
//    for (l = 0; l < 2; l++) {
//        fd_set readfds;
//        struct timeval timeout;
//        /* Wait for data to be echoed back */
//        FD_ZERO(&readfds);
//        FD_SET(s, &readfds);
//        timeout.tv_sec = TIMEOUT;
//        timeout.tv_usec = 0;
//
//        if (select(s + 1, &readfds, NULL, NULL, &timeout) != 1) {
//            fprintf(stderr, "Clean select fail @loop - %d.\n", l);
//            return;
//        }
//
//        int nr = recv(s, rxBuff, BUFSIZE, 0);
//        if (nr <= 0) {
//            fprintf(stderr, "Clean receive fail @ %d, (%d)\n", l, nr);
//            return;
//        }
//    }
//
//    return;
//}

static char * name;
static struct addrinfo *results = NULL;
static struct addrinfo hints;
static SOCKET s;
static int status = EXIT_SUCCESS;
static char portString[10];
static unsigned short round = ROUND;

/*
 *  ======== main ========
 */
int main(int argc, char *argv[]) {
    int             ret  = 0;
    unsigned int   loops = LOOPS;


    name = argv[0];
    if (argc < 2) {
        fprintf(stderr, "Usage: %s [ip] [port] [len] [loops] [round] [up]\n", name);
        fprintf(stderr, "Start default test - %s:%d with length %d and %d round with %d loops, no update.\n",
                defIp, PORT, UDP_IPv4_MAXPAYLOAD, ROUND, LOOPS);
    }

    /* parse XXX */
    if (argc >= 2)      //ip
        defIp = argv[1];

    unsigned short port = PORT;
    if (argc >= 3) {    //port
        port = (unsigned short)strtoul(argv[2], NULL, 0);
    }
    /* convert the port into string format in order to pass it to getaddrinfo */
    sprintf(portString,"%d", port);

    /* initialize sockets environment */
    socketsStartup();

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;

    /*
     * getaddrinfo() fills in the results struct for us appropriately
     * depending on whether the IP address is v4 or v6
     *
     *      argv[1] = IPv4 or IPv6 address passed in from command line
     *      argv[2] = port number passed in from command line */
    ret = getaddrinfo(defIp, portString, &hints, &results);

    if (ret != 0) {
        fprintf(stderr, "getaddrinfo failed: %d\n", ret);
        if (ret == -2 || ret == 11004) {
            fprintf(stderr, "unrecognized IP address\n");
        }
        status = EXIT_FAILURE;
        goto QUIT;
    }


    short UDP_HIGH = UDP_IPv4_MAXPAYLOAD;
    /* set max UDP payload accordingly for IPv4/IPv6 to avoid fragmentation */
    if (results->ai_family == AF_INET) {
        UDP_HIGH = UDP_IPv4_MAXPAYLOAD;
        fprintf(stderr, "IPv4 address\n");
    }
    else if (results->ai_family == AF_INET6) {
        UDP_HIGH = UDP_IPv6_MAXPAYLOAD;
        fprintf(stderr, "IPv6 address\n");
    }
    else {
        fprintf(stderr, "error: unknown protocol family: %d\n",
                results->ai_family);
        status = EXIT_FAILURE;
        goto QUIT;
    }

    //Test info
    fprintf(stderr, "\nTesting target client at %s:%d\n", argv[1], port);

    /* create socket. ai_family determined for us via getaddrinfo() call */
    if ((s = socket(results->ai_family, results->ai_socktype, 0)) < 0) {
        fprintf(stderr, "%s: failed socket (%d)\n", name, getError());
        status = errno;
        goto QUIT;
    }


    //TODO add input
    char mode = 'S';
    int testsize = UDP_HIGH;

    int ec = 0;

    char*           tbuf;
    unsigned        loop = 0;
    unsigned        rid = 0;
    uint64_t        sizeAll = 0;

    struct timeval  ts, td;

    struct timeval  timeout;
    timeout.tv_sec = TIMEOUT;
    timeout.tv_usec = 0;

    while (round != 0) {
        if (loop ==0) {
            //clearRecv(s);
            gettimeofday(&ts, NULL);
        }

        TX_DEAL:
        tbuf = &txBuff[loop++ & 1][0];
        *(unsigned*) tbuf = loop;

        if (mode == 'U')//TODO update mode
            if (testsize++ > UDP_HIGH)
                testsize = UDP_LOW;

        if ((ret = sendto(s, tbuf, testsize, 0, results->ai_addr, results->ai_addrlen)) < 0) {
            gettimeofday(&td, NULL);
            fprintf(stderr, "send failed (%d) @r%dl%d\n", getError(), round, loop);
            status = errno;
            loop += (loops + 5);
            goto ROUND_LOOP_EXIT;
        }

        //send more
        if (loop < 3)
            goto TX_DEAL;


        fd_set readfds;
        /* Wait for data to be echoed back */
        FD_ZERO(&readfds);
        FD_SET(s, &readfds);

        if (select(s + 1, &readfds, NULL, NULL, &timeout) != 1) {
            gettimeofday(&td, NULL);
            //TODO FIXME fprintf(stderr, "%s: failed on size %d's receive @loop - %d.\n", name, testsize, loop);
            status = errno;
            goto ROUND_LOOP_EXIT;
        }

        ret = recv(s, rxBuff, /*TODO BUFSIZE*/testsize, 0);
        if (ret <= 0) {
            fprintf(stderr, "%s: unexpected return from recv (%d)\n", name, ret);
            status = errno;
            goto QUIT;
        }
        //check receive
        if ((++rid != *((unsigned*) rxBuff)) && (mode != 'S')) {
            if (rid == 1) {//skip first receive's !0 for cleaning last sended.
                rid--;
                if (ec++ < ECMAX)
                    continue;
            }
            gettimeofday(&td, NULL);
            loop += loops;
            goto ROUND_LOOP_EXIT;
        }
        rid++;
        sizeAll += ret;


        /* Dump test info. for each round if round ended */
        ROUND_LOOP_EXIT:
        if (loop >= loops) {
            if (loop == loops)
                gettimeofday(&td, NULL);
            //summary
            uint64_t us = (td.tv_sec - ts.tv_sec) * 1000000 + td.tv_usec - td.tv_usec;
            float Bps = sizeAll / (us / 1000000.0f) / 1000.0f;
            //info.
            if (loop == loops)
                fprintf(stderr, "Test %d loop passed from %ld\"%ldus to %ld\"%ldus(%ldus used), received - %ld, speed - %fKBps, continue...\n",
                       loops, ts.tv_sec, ts.tv_usec, td.tv_sec, td.tv_usec, us, sizeAll, Bps);
            else
                fprintf(stderr, "Test faild @ %d/%d from %ld\"%ldus to %ld\"%ldus(%ldus used), received - %ld, speed - %fKBps, continue...\n",
                       rid, loops, ts.tv_sec, ts.tv_usec, td.tv_sec, td.tv_usec, us, sizeAll, Bps);
            //reset
            ec = 0;
            rid = 0;
            loop = 0;
            round--;
            sizeAll = 0;
        }
    }


    status = EXIT_SUCCESS;

    QUIT:
    (void) close(s);


    return (status);
}
